package smallChanges.lineNumberMapping;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;

import smallChanges.statements.MethodDeclarationStatement;
import smallChanges.statements.MethodSignature;
import smallChanges.statements.Statement;
import smallChanges.statements.StatementMappingEntry;
import smallChanges.statements.StatementMappingResultSet;

public class RenamedMethodComparison implements RenamedSourceObjectComparison{

	private boolean matchedClass;
	private boolean matchedName;
	private boolean matchedVisibility;
	private boolean matchedReturnType;
	private double  normalizedNameSimilarity;
	private boolean matchedAbstract;
	private boolean matchedFinal;
	private boolean matchedStatic;
	private boolean matchedSynchronized;
	private RenamedMethodParameterListComparison matchedParamList;
	
	// match/replace tokens?
	// 1.0 has to be completely similar 
	// 0.0 has to be not at all similar
	private double bodySimilarity; // dice?

	public RenamedMethodComparison(MethodDeclarationStatement leftDecl, MethodDeclarationStatement rightDecl,
			ClassSource leftClass, ClassSource rightClass, StatementMapper sm)
	{
		//MethodSignature left = (MethodSignature) leftDecl.getMethodSignatureStatement();
	    //MethodSignature right = (MethodSignature) rightDecl.getMethodSignatureStatement();
	    MethodSignature left = (MethodSignature) leftDecl.getHeaderStmt();
	    MethodSignature right = (MethodSignature) rightDecl.getHeaderStmt();
		
	
		matchedClass = true; /* we don't have class info! */
		matchedName = false;
		normalizedNameSimilarity = 0.0;
		matchedAbstract = true;
		matchedSynchronized = true;
		matchedStatic = true;
		matchedFinal = true;
		if ( left.getModifiers() != null && right.getModifiers() != null)
		{

			if (left.isAbstract() != right.isAbstract())
			{
				matchedAbstract = false;
			}
			if(	left.isFinal() != right.isFinal())
			{
				matchedFinal = false;
			}
			if(	left.isStatic() != right.isStatic() )
			{
				matchedStatic = false;
			}
			if(left.isSynchronized() != right.isSynchronized())
			{
				matchedSynchronized = false;	
			}
		}	
		if( left.getName() != null && right.getName() != null)
		{
			matchedName = left.getName().equals(right.getName());

			normalizedNameSimilarity = (1- 
				org.apache.commons.lang.StringUtils.getLevenshteinDistance(left.getName(), right.getName()) /
					(Math.max(left.getName().length(), right.getName().length())));
			
			normalizedNameSimilarity = Math.pow(normalizedNameSimilarity, 2.0);
		}
		matchedVisibility = left.getVisibility() == right.getVisibility(); /* singleton */
		if( left.getReturnTypeAsString() != null)
		{
			matchedReturnType = left.getReturnTypeAsString().equals(right.getReturnTypeAsString());
		}
		else 
		{
			matchedReturnType = right.getReturnTypeAsString() == null;
		}
		
		matchedParamList = new RenamedMethodParameterListComparison(left, right);

		//bodySimilarity = findSimilarity(leftDecl, rightDecl);
		
		String leftMethodSig = leftDecl.getSignature();
		ArrayList<Statement> leftMethodStatements = new ArrayList<Statement>();
		ArrayList<Statement> rightMethodStatements = new ArrayList<Statement>();

		ArrayList<String> leftMethodBody = leftClass.getCleanedOutMethodBody(leftMethodSig,
				leftMethodStatements);
		ArrayList<String> rightMethodBody = rightClass.getCleanedOutMethodBody(rightDecl.getSignature(),
				rightMethodStatements);
		String rightMethodSig=rightDecl.getSignature();
		int leftMethodStartLine=leftClass.getMethodStartLine(leftMethodSig);
		int rightMethodStartLine=rightClass.getMethodStartLine(rightMethodSig);

		// cut this off here
		StatementMappingResultSet resultSet = new StatementMappingResultSet();
		try {
			// part of the renaming is to figure out how well the statements
		    // match up between the two
		    sm.mapMethod(leftMethodSig, leftMethodBody, leftMethodStatements,
					leftMethodStartLine,rightMethodBody, 
					rightMethodStatements, rightMethodStartLine, resultSet,
					false);
            int count=0;
            
            Iterator<StatementMappingEntry> iter = resultSet.iterator();
            while(iter.hasNext())
            {
            	if(iter.next().getNormalizedEditDistance() <= sm.getCanfora())
            	{
            		count++;
            	}
            }
			//bodySimilarity = (((double) count) / Math.max(leftMethodStatements.size() , rightMethodStatements.size()));
			bodySimilarity = (((double) count) / ( (leftMethodStatements.size() + rightMethodStatements.size())/2 ));
			if (count == 0)
			{
				bodySimilarity = 0.0;
			}
		} catch (IOException e) {
			System.err.println("RenamedMethodComparison:");
			e.printStackTrace();
		}

	}
	
	public double getComparisonStatistic()
	{
		/* higher is more similar */
		double retVal = 0.0;
		// max of 5
		if( matchedAbstract)
		{
			retVal += 0.25;
		}
		if(matchedFinal)
		{
			retVal += 0.25;
		}
		if(matchedStatic)
		{
			retVal += 0.25;
		}
		if(matchedSynchronized)
		{
			retVal += 0.25;
		}
		
		if( matchedReturnType)
		{
			retVal += 0.5;
		}
		if( matchedVisibility)
		{
			retVal += 0.5;
		}
		if( matchedName )
		{
			retVal += 1.0;
		}
		//else
		//{
		//	retVal += normalizedNameSimilarity;
		//}
		if( matchedClass )
		{
			retVal += 0.5;
		}
		retVal += (1-matchedParamList.getNormalizedParamListEditDist());
		retVal += (bodySimilarity);
		
		if(matchedName && 0.0 == matchedParamList.getNormalizedParamListEditDist())
		{
			// name and parameter list match exactly
			return this.getComparisonStatisticMax();
		}
		return retVal;
	}
	
	private String buildBody(MethodDeclarationStatement method)
	{
		String body = null;
		
		method.flatten();
		Iterator<Statement> iter = method.getBody().iterator();
		while(iter.hasNext())
		{
			body += iter.next().getImage();
		}
		return body;		
	}
	private double findSimilarity(MethodDeclarationStatement left, MethodDeclarationStatement right)
	{
		String leftBody, rightBody;
		double retVal = 0;

		leftBody = buildBody(left);
		rightBody = buildBody(right);
		if(  (leftBody!= null && rightBody != null) )
		{

			simpack.measure.external.simmetrics.DiceSimilarity dice = new simpack.measure.external.simmetrics.DiceSimilarity(leftBody, rightBody);
			retVal = dice.getSimilarity();
		}
		return retVal;

	}
	
	public boolean matchedParameterList()
	{
		return matchedParamList.matched();
	}
	
	public boolean isMatchedClass() {
		return matchedClass;
	}

	public void setMatchedClass(boolean matchedClass) {
		this.matchedClass = matchedClass;
	}

	public boolean isMatchedName() {
		return matchedName;
	}

	public void setMatchedName(boolean matchedName) {
		this.matchedName = matchedName;
	}

	public boolean isMatchedVisibility() {
		return matchedVisibility;
	}

	public void setMatchedVisibility(boolean matchedVisibility) {
		this.matchedVisibility = matchedVisibility;
	}

	public boolean isMatchedReturnType() {
		return matchedReturnType;
	}

	public void setMatchedReturnType(boolean matchedReturnType) {
		this.matchedReturnType = matchedReturnType;
	}

	public double getBodySimilarity() {
		return bodySimilarity;
	}
	
	public double getComparisonStatisticMax()
	{
		return 5.5;
	}

	public void setBodySimilarity(double bodySimilarity) {
		this.bodySimilarity = bodySimilarity;
	}
	
}
